using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Xml;
using System.Collections;
using SharpObjects.Configuration.ConfigurationProviders;
using System.Diagnostics;
using SharpObjects.Multithreading;

namespace SharpObjects.Logging.LogTargets 
{
	public class EventLogTarget : LogTargetBase
	{
        protected static BinaryFormatter binaryFormatter = new BinaryFormatter();

        public static void Save(object obj, string file)
        {
            FileStream fileStream = new FileStream(file, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
            fileStream.Seek(0, SeekOrigin.End);
            binaryFormatter.Serialize(fileStream, obj);
        }

	    protected string logName = null;
        protected string source = null;
        protected string targetMachineName = null;
        protected bool backupAndCleanup = false;
        protected int maxEntriesBeforeBackup = -1;
        protected bool overwriteAsNeeded;
        protected EventLog eventLog = null;
        protected const string syncName = "SharpObjects.Logging.LogTargets.EventLogTarget";

		public EventLogTarget()
		{
		    this.maxSize = -1;
		}

		public EventLogTarget(string sLogName)
		{
            this.maxSize = -1;
		}


		public override void Release()
		{
		}

        /*
          <event_log_target is_enabled="true">
            <formatter pathref="//formatters/default_log_formatter"/>
            <source_name>SharpObjects_Logging_Test</source_name>
            <log_name>SharpObjects_Logging_TestLog</log_name>
            <target_machine_name>localhost</target_machine_name>
            <log_max_size units="kb">2000</log_max_size>
            <backup_and_cleanup>true</backup_and_cleanup>
            <max_entries_before_backup>2000</max_entries_before_backup>
            <overwrite_as_needed>true</overwrite_as_needed>
          </event_log_target>


        */

        public override void Initialize(XmlElement initializationElement)
		{
            base.Initialize(initializationElement);

            if(this.maxSize != -1)
            {
                this.maxSize = this.maxSize/1024; // must be in KB
            }

			XmlConfigurationProvider init = new XmlConfigurationProvider(initializationElement);

            this.logName = init.GetString("log_name");
            this.source = init.GetString("source_name");
            CreateMutex(this.logName);

            this.targetMachineName = init.GetString("target_machine_name", ".");
            this.backupAndCleanup = init.GetBoolean("backup_and_cleanup", false);
            this.maxEntriesBeforeBackup = init.GetInteger("max_entries_before_backup", -1);
            this.overwriteAsNeeded = init.GetBoolean("overwrite_as_needed", true);

            if (this.targetMachineName.ToLower() == "localhost")
            {
                this.targetMachineName = ".";
            }

            using (ISyncLock syncLock = new MutexLock(syncName, true))
            {
                if (EventLog.SourceExists(this.source, this.targetMachineName))
                {
                    string existingLogName = EventLog.LogNameFromSourceName(this.source, this.targetMachineName);

                    if (existingLogName.ToLower() != this.logName.ToLower())
                    {
                        throw new Exception(string.Format("Event Log Source Name: {0} already assigned to the {1} log instead of {2} log", this.source, existingLogName, this.logName));
                    }
                } 
                
                if (!EventLog.SourceExists(this.source, this.targetMachineName))
                {
                    EventSourceCreationData sourceData = new EventSourceCreationData(this.source, this.logName);
                    sourceData.MachineName = this.targetMachineName;
                    EventLog.CreateEventSource(sourceData);
                }
            }

            this.eventLog = new EventLog(this.logName, this.targetMachineName, this.source);

            if (this.overwriteAsNeeded)
            {
                this.eventLog.ModifyOverflowPolicy(OverflowAction.OverwriteAsNeeded, 0);
            }

            if (this.maxSize >= 0)
            {
                this.eventLog.MaximumKilobytes = this.maxSize;
            }

		}

		protected override void WriteToLog(ILogEntry logEntry)
		{
			string sLogMsg = this.formatter.ApplyFormat(logEntry);
            EventLogEntryType type;

            if (logEntry.Category.ToUpper() == "WARNING")
            {
                type = EventLogEntryType.Warning;
            }
            else if (logEntry.Category.ToUpper() == "ERROR")
            {
                type = EventLogEntryType.Error;
            }
            else
            {
                type = EventLogEntryType.Information;
            }

            BackupAndCleanupIfNeeded();

			
            if (this.eventLog != null)
            {
                this.eventLog.WriteEntry(sLogMsg, type); 
            }
        }

        protected void BackupAndCleanupIfNeeded()
        {
            Exception exception = null;

            using (ISyncLock syncLock = new MutexLock(syncName, true))
            {
                if (this.backupAndCleanup && this.eventLog.Entries.Count >= this.maxEntriesBeforeBackup)
                {
                    try
                    {
                        DateTime now = DateTime.Now;
                        EventLogEntry[] entries = new EventLogEntry[this.eventLog.Entries.Count];
                        this.eventLog.Entries.CopyTo(entries, 0);
                        Save(entries, string.Format("{0}_({1}-{2}-{3} {4}_{5}_{6}).evlog", this.logName, now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second));
                        
                    }
                    catch (Exception ex)
                    {
                        exception = ex;
                    }
                    finally
                    {
                        this.eventLog.Clear();
                    }
                }
            }

            if (exception != null)
            {
                LogEntry logEntry = new LogEntry(true);
                logEntry.Message = exception.Message;
                logEntry.StackTrace = exception.StackTrace;
                logEntry.Origin = "SharpObjects.Logging.LogTargets.EventLogTarget.BackupAndCleanupIfNeeded()";
                logEntry.Category = "ERROR";
                Write(logEntry);
            }
        }

        protected override void Close()
        {
            if (this.eventLog != null)
            {
                this.eventLog.Close();
                this.eventLog = null;
            }
        }
	}
}
